using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Kaleida.ServiceMonitor.Framework
{
    internal class DurationParser
    {
        private static readonly IList<DurationParser> parsers = new[]
                                                                     {
                                                                         new DurationParser("day", "days", new[]{"d"}, TimeSpan.FromDays),
                                                                         new DurationParser("hr", "hrs", new[]{"h", "hour", "hours"}, TimeSpan.FromHours),
                                                                         new DurationParser("min", "mins", new[]{"m", "minute", "minutes"}, TimeSpan.FromMinutes),
                                                                         new DurationParser("sec", "secs", new[]{"s", "second", "seconds"}, TimeSpan.FromSeconds),
                                                                         new DurationParser("ms", "ms", new[]{"ms", "millisecond", "milliseconds", "milli second", "milli seconds", "milli-second", "milli-seconds", ""}, TimeSpan.FromMilliseconds)
                                                                     };

        private readonly IList<Regex> patterns;
        private readonly string canonicalSuffix;
        private readonly string pluralCanonicalSuffix;
        private readonly IEnumerable<string> permittedAlternativeSuffices;
        private readonly Func<double, TimeSpan> converter;

        public static bool CanParse(string text)
        {
            return parsers.Any(i => i.CanAccept(text));
        }

        public static TimeSpan ParseTimeSpan(string text)
        {
            return GetSpecificParser(text).BuildTimeSpan(text);
        }

        public static Duration ParseDuration(string text)
        {
            return GetSpecificParser(text).BuildDuration(text);
        }

        private static DurationParser GetSpecificParser(string text)
        {
            var parser = (from i in parsers
                          where i.CanAccept(text)
                          select i).FirstOrDefault();

            if (parser == null)
            {
                var supportedSuffices = parsers.Select(i => i.CanonicalSuffix);
                string validSuffices = string.Join(", ", supportedSuffices.Where(i => i.Trim() != ""));
                throw new FormatException(string.Format("Cannot parse '{0}' as a duration. The following suffices may be used: {1}", text, validSuffices));
            }

            return parser;
        }


        public DurationParser(string canonicalSuffix, string pluralCanonicalSuffix, IList<string> permittedAlternativeSuffices, Func<double, TimeSpan> converter)
        {
            this.canonicalSuffix = canonicalSuffix;
            this.pluralCanonicalSuffix = pluralCanonicalSuffix;
            this.permittedAlternativeSuffices = permittedAlternativeSuffices;
            this.converter = converter;

            patterns = SupportedSuffices.Select(i => new Regex(@"^\s*(-?[0-9.,]+)\s*" + i + "$", RegexOptions.Compiled)).ToList();
        }

        public bool CanAccept(string text)
        {
            var preparedText = TrimAndLowercase(text);
            return patterns.Any(i=>i.IsMatch(preparedText));
        }

        public Duration BuildDuration(string text)
        {
            var value = GetValue(text);
            
            var suffix = GetSuffix(value);

            return new Duration(value, suffix);
        }

        private string GetSuffix(double value)
        {
            const double epsilon = 0.00001;
            var isSingular = Math.Abs(Math.Abs(value) - 1) < epsilon;
            var suffix = isSingular ? canonicalSuffix : pluralCanonicalSuffix;
            return suffix;
        }

        public TimeSpan BuildTimeSpan(string text)
        {
            var value = GetValue(text);
            return converter(value);
        }

        public IEnumerable<string> SupportedSuffices
        {
            get { return new[] {canonicalSuffix, pluralCanonicalSuffix}.Concat(permittedAlternativeSuffices); }
        }

        public string CanonicalSuffix
        {
            get { return canonicalSuffix; }
        }

        private double GetValue(string text)
        {
            var preparedText = TrimAndLowercase(text);
            var regex = patterns.FirstOrDefault(i => i.IsMatch(preparedText));
            if(regex == null)
                throw new InvalidOperationException(string.Format("Cannot parse '{0}' with the {1} DurationParser", text, canonicalSuffix));

            var match = regex.Match(preparedText);
            if (!match.Success || match.Groups.Count != 2)
                throw new InvalidOperationException(String.Format("Expected '{0}' to product 2 groups using pattern '{1}'", text, regex));

            double value;
            if (!Double.TryParse(match.Groups[1].Value, out value))
                throw new InvalidOperationException(String.Format("Couldn't convert '{0}' to a double", match.Groups[1].Value));
            return value;
        }

        private static string TrimAndLowercase(string text)
        {
            return text.Trim().ToLowerInvariant();
        }
    }
}